using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Linq;

namespace GuavaData
{
    public abstract class Operator : Object
    {
        public virtual bool isQuery
        {
            get
            {
                return true;
            }
        }

        public const string GuavaFlag = "GNode";
        public const string DomainFlag = "GDomain";

        internal List<string> exposedColumns;
        internal List<Domain.Domain> exposedDomains;
        public Operator parent;
        public virtual SqlText Visit(Provider qv)
        {
            return qv.Transform(this);
        }
        public virtual void Visit(Transform t, Command c)
        {
            t.Expand(this, c);
        }
        public virtual void Visit(Channel c, string table)
        {
            c.MapAliases(this, table);
        }

        public abstract void NotifyChange(Operator old, Operator newOp);
        public abstract Operator Clone();

        public static Operator FromXElement(XElement xe)
        {
            if (xe.Name.LocalName != "operator")
                throw new Exception("XElement is not of type Operator");

            // What type should the new transfrom be?
            var type = xe.Attribute("type").Value;

            if (type == "tableRef")
                return TableRef.FromXElement(xe);
            if (type == "filter")
                return Filter.FromXElement(xe);
            if (type == "project")
                return Project.FromXElement(xe);
            if (type == "join")
                return Join.FromXElement(xe);

            throw new Exception("Operator type is unknown");
        }
    }

    public interface Provider : Cell
    {
        SqlText Transform(AddColumn ac);
        SqlText Transform(AddElement ae);
        SqlText Transform(AddTable at);
        SqlText Transform(AlterColumnRename acr);
        SqlText Transform(AlterElementRename aer);
        SqlText Transform(AlterTableRename atr);
        SqlText Transform(AntiSemiJoin asj);
        SqlText Transform(ApplyOp a);
        SqlText Transform(CrossJoin cj);
        SqlText Transform(Delete d);
        SqlText Transform(DropColumn dc);
        SqlText Transform(DropElement de);
        SqlText Transform(DropTable dt);
        SqlText Transform(Except e);
        SqlText Transform(Filter op);
        SqlText Transform(ForeignKey a);
        SqlText Transform(Insert a);
        SqlText Transform(Intersect i);
        SqlText Transform(Join op);
        SqlText Transform(Operator op);
        SqlText Transform(PivotOp po);
        SqlText Transform(Project p);
        SqlText Transform(RowConstant rc);
        SqlText Transform(TableRef tr);
        SqlText Transform(Union u);
        SqlText Transform(UnpivotOp uo);
        SqlText Transform(Update u);
    }

    public class SqlText : Object
    {
        private StringBuilder _command;
        public List<string> projectColumns = new List<string>();
        public List<string> projectAliases = new List<string>();
        public StringBuilder _from;
        public StringBuilder _where;
        private bool distinct = false;
        public static bool IsOracle = false;         // oracle provider
        public bool useSquareParaAlias = false;     // access provider
        public static bool IsPostgres = false;

        public Boolean IsDistinct
        {
            get
            {
                return distinct;
            }
            set
            {
                distinct = value;
            }
        }

        public bool hasNoCommand
        {
            get
            {
                return _command == null;
            }
        }
        public StringBuilder Command
        {
            get
            {
                if (_command == null)
                {
                    _command = new StringBuilder();
                }
                return _command;
            }
            set
            {
                _command = value;
            }
        }

        public bool hasNoFrom
        {
            get
            {
                return _from == null;
            }
        }
        public StringBuilder From
        {
            get
            {
                if (_from == null)
                {
                    _from = new StringBuilder();
                }
                return _from;
            }
            set
            {
                _from = value;
            }
        }

        public bool hasNoWhere
        {
            get
            {
                return _where == null;
            }
        }
        public StringBuilder Where
        {
            get
            {
                if (_where == null)
                {
                    _where = new StringBuilder();
                }
                return _where;
            }
            set
            {
                _where = value;
            }
        }

        public override string ToString()
        {
            if (_command != null)
                return _command.ToString();
            if (_from == null)
            {
                // We have a row constant
                return String.Format("VALUES ({0})", String.Join(", ", projectColumns.ToArray()));
            }
            if (_where == null)
            {
                return String.Format("SELECT {0} FROM {1}",
                Select,
                _from.ToString());
            }
            return String.Format("SELECT {0} FROM {1}{2}",
                Select,
                _from.ToString(),
                ((_where.ToString() == "") ? "" : " WHERE " + _where.ToString()));
        }

        public string ToUpdateString(string table, IEnumerable<string> columns)
        {
            if (_command != null)
                throw new Exception("Cannot create an update string with either a command or an empty from list.");

            List<string> setColumns = new List<string>(columns);
            var sb = new StringBuilder();
            sb.Append(setColumns[0]);
            sb.Append("=");
            sb.Append(projectColumns[0] == "" ? projectAliases[0] : projectColumns[0]);

            for (int i = 1; i < projectColumns.Count; i++)
            {
                sb.Append(", ");
                sb.Append(setColumns[i]);
                sb.Append("=");
                sb.Append(projectColumns[i] == "" ? projectAliases[i] : projectColumns[i]);
            }

            return String.Format("UPDATE {0} SET {1}{2}{3}", table, sb.ToString(),
                ((_from == null) ? "" : " FROM " + _from.ToString()),
                ((_where == null) ? "" : " WHERE " + _where.ToString()));

        }

        private string Select
        {
            get
            {
                if (projectAliases.Count <= 0)
                    return "*";
                var sb = new StringBuilder();
                if (IsDistinct)
                    sb.Append("DISTINCT ");
                if (IsOracle)
                {
                    sb.Append(projectColumns[0] == "" ? "" : (projectColumns[0] + " AS "));
                    if (projectAliases[0].IndexOf("\"") != -1)
                        sb.Append(projectAliases[0]);
                    else
                        sb.Append("\"" + projectAliases[0].Replace("\"", "") + "\"");
                }
                else if (IsPostgres)
                {
                    sb.Append(projectColumns[0] == "" ? "" : (projectColumns[0] + " AS "));
                    sb.Append("\"" + projectAliases[0].Replace("\"", "") + "\"");
                }
                else
                {
                    sb.Append(projectColumns[0] == "" ? "" : (projectColumns[0] + " AS "));
                    if (useSquareParaAlias)
                        sb.Append("[" + projectAliases[0] + "]");//.Split('.')[1]);
                    else
                        sb.Append("\'" + projectAliases[0].Replace("\"", "") + "\'");//.Split('.')[1]);
                }

                for (int i = 1; i < projectColumns.Count; i++)
                {
                    sb.Append(", ");
                    if (IsOracle)
                    {
                        sb.Append(projectColumns[i] == "" ? "" : (projectColumns[i] + " AS "));
                        if (projectAliases[i].IndexOf("\"") != -1)
                            sb.Append(projectAliases[i]);
                        else
                            sb.Append("\"" + projectAliases[i].Replace("\"", "") + "\"");
                    }
                    else if (IsPostgres)
                    {
                        sb.Append(projectColumns[0] == "" ? "" : (projectColumns[i] + " AS "));
                        sb.Append("\"" + projectAliases[i].Replace("\"", "") + "\"");
                    }
                    else
                    {
                        sb.Append(projectColumns[i] == "" ? "" : (projectColumns[i] + " AS "));
                        if (useSquareParaAlias)
                            sb.Append("[" + projectAliases[i] + "]");//.Split('.')[1]);
                        else
                            sb.Append("\'" + projectAliases[i] + "\'");//.Split('.')[1]);
                    }
                }

                return sb.ToString();
            }
        }

        public void ClearProjection()
        {
            this.projectAliases.Clear();
            this.projectColumns.Clear();
        }

        public string this[string s]
        {
            get
            {
                if (this.projectAliases.Contains(s))
                    return this.projectColumns[this.projectAliases.IndexOf(s)];
                else
                    throw new Exception("Element not found.");
            }
            set
            {
                if (this.projectAliases.Contains(s))
                    this.projectColumns[this.projectAliases.IndexOf(s)] = value;
                else
                {
                    this.projectAliases.Add(s);
                    this.projectColumns.Add(value);
                }
            }
        }

        public string GetAlias(string s)
        {
            if (this.projectColumns.Contains(s))
                return this.projectAliases[this.projectColumns.IndexOf(s)];
            else
                throw new Exception("Element not found.");
        }

        public void Add(string alias, string value)
        {
            this.projectAliases.Add(alias);
            this.projectColumns.Add(value);
        }

        internal void Remove(string s)
        {
            this.projectColumns.RemoveAt(this.projectAliases.IndexOf(s));
            this.projectAliases.Remove(s);
        }

    }
}
